package com.google.code.mircle.core.transfers;

import java.io.File;
import java.net.URL;

import com.github.axet.vget.VGet;
import com.github.axet.vget.info.VideoInfo;
import com.github.axet.vget.info.VideoInfo.VideoQuality;
import com.github.axet.wget.SpeedInfo;
import com.github.axet.wget.info.DownloadError;
import com.github.axet.wget.info.DownloadRetry;
import com.google.code.mircle.core.Worker;
import com.thoughtworks.xstream.annotations.XStreamAlias;

/**
 * 
 * @author axet
 * 
 */
public class VGetDownload extends Worker {

    @XStreamAlias("YouTubeState")
    static class State extends Worker.State {
        File target;

        VideoInfo info;
        File targetFile;
        Exception ee = null;
    }

    State state = new State();
    int delay = 0;

    Object joinLock = new Object();

    VGet vget;

    SpeedInfo speedInfo = new SpeedInfo();

    final long MAX_UPDATE_TIME = 10;

    public VGetDownload(URL source, File target) {
        this.state.info = VGet.extract(source);
        this.state.target = target;
    }

    public VGetDownload() {

    }

    public static boolean handle(String url) {
        return url.indexOf("youtube.com/watch") != -1;
    }

    @Override
    protected State getState() {
        return state;
    }

    void ytChanged() {
        synchronized (joinLock) {
            joinLock.notify();
        }
    }

    @Override
    public void stop() {
        super.stop();

        if (vget != null)
            vget.stop();

        synchronized (joinLock) {
            joinLock.notify();
        }
    }

    @Override
    public void close() {
        super.close();

        if (vget != null)
            vget.stop();

        synchronized (joinLock) {
            joinLock.notify();
        }

        if (vget != null)
            vget.join();

        sync();
    }

    void join() throws Throwable {
        synchronized (joinLock) {
            try {
                joinLock.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        Exception e = vget.getException();
        if (e != null) {
            throw e;
        }
    }

    @Override
    public void run() {
        synchronized (state) {
            if (state.downloadState == DownloadState.DOWNLOAD_COMPLETE && getTarget().exists()) {
                return;
            }
        }

        synchronized (state) {
            if (vget == null) {
                vget = new VGet(state.info, state.target);
                vget.addListener(new VGet.Listener() {
                    @Override
                    public void changed() {
                        ytChanged();
                    }
                });
            }
        }

        while (!stop.get()) {
            synchronized (state) {
                state.ee = null;
                // keep target between restarts only if file present and the
                // same size
                if (state.targetFile != null) {
                    if (state.targetFile.exists() && state.info.getInfo().getCount() == state.targetFile.length())
                        vget.setTarget(state.targetFile);
                }
            }

            vget.start();

            synchronized (state) {
                speedInfo.start(state.info.getInfo().getCount());
            }

            sync();

            long lastUpdate = 0;

            try {
                while (vget.isActive()) {
                    join();

                    long now = System.currentTimeMillis();

                    if (vget.isJoin()) {
                        vget.join();

                        if (!vget.canceled()) {
                            synchronized (state) {
                                state.downloadState = DownloadState.DOWNLOAD_COMPLETE;
                            }
                        }

                        sync();

                        return;
                    }

                    if (lastUpdate + MAX_UPDATE_TIME < now) {
                        lastUpdate = now;

                        sync();
                    }
                }
            } catch (DownloadError e) {
                synchronized (state) {
                    state.ee = e;
                    state.downloadState = DownloadState.DOWNLOAD_FAILED;
                }
            } catch (DownloadRetry e) {
                synchronized (state) {
                    state.ee = e;
                }
                retry();
            } catch (RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }
        }
    }

    void reset() {
        synchronized (state) {
            state.info.getInfo().reset();
        }
    }

    void sync() {
        synchronized (state) {
            speedInfo.step(state.info.getInfo().getCount());
        }

        changed();
    }

    void retry() {
        for (int i = 10; i >= 0; i--) {
            if (stop.get())
                return;

            changed();

            try {
                Thread.sleep(1000);
            } catch (Exception r) {
                throw new RuntimeException(r);
            }

            synchronized (state) {
                delay = i;
            }
        }
    }

    @Override
    public Status status() {
        synchronized (state) {
            return new Status(state.info.getInfo().getLength(), state.info.getInfo().getCount(), state.info.getTitle(),
                    delay, speedInfo.getCurrentSpeed(), speedInfo.getAverageSpeed());
        }
    }

    public Exception getException() {
        synchronized (state) {
            return state.ee;
        }
    }

    public File getTarget() {
        synchronized (state) {
            return state.targetFile;
        }
    }

    @Override
    public Worker.State save() {
        return state;
    }

    @Override
    public void load(Worker.State s) {
        state = (State) s;
    }

    @Override
    public String getSource() {
        synchronized (state) {
            return state.info.getSource().toString();
        }
    }

}
